package math.petrinets.octave;

import dk.ange.octave.OctaveEngine;
import dk.ange.octave.OctaveEngineFactory;
import dk.ange.octave.exception.OctaveEvalException;
import dk.ange.octave.exception.OctaveIOException;
import dk.ange.octave.type.OctaveDouble;
import dk.ange.octave.type.OctaveInt;

/**
 * Utility class to use the GNU Octave program to multiply matrices and vectors.
 * Define a series of handy method to perform those actions.
 * 
 * All the operations accept only double type variables.
 * 
 * @author Rogelio Delgado González (roge.delgado@gmail.com)
 * 
 */
public class OctaveUtils {

    /**
     * Multiply the given matrices and return the result matrix using Octave.
     * 
     * @param m1Id
     *            A identifier of the first matrix.
     * @param matrix1
     *            The first matrix
     * @param m2Id
     *            A identifier of the second matrix.
     * @param matrix2
     *            The second matrix.
     * @return The matrix that result of the multiply operation.
     */
    public int[][] multiplyMatrixByMatrix(String m1Id, int[][] matrix1, String m2Id, int[][] matrix2) throws OctaveIOException {
	try {
	    OctaveEngine octave = new OctaveEngineFactory().getScriptEngine();
	    octave.eval(transformMatrixToOctave(m1Id, matrix1));
	    octave.eval(transformMatrixToOctave(m2Id, matrix2));
	    octave.eval("result = " + m1Id + "*" + m2Id);
	    OctaveDouble result = octave.get(OctaveDouble.class, "result");
	    int[][] resultM = transformOctaveToJava(matrix1.length, matrix2[0].length, result);
	    return resultM;
	} catch (OctaveEvalException e) {
	    throw new IllegalArgumentException(e.getMessage());
	}
    }

    /**
     * Multiply the given matrix and vector using the Octave program.
     * 
     * @param matrixId
     *            A identifier of the matrix.
     * @param matrix
     *            The matrix to multiply
     * @param vectorId
     *            A identifier for the vector
     * @param vector
     *            The vector to multiply
     * @return The result of the multiply process.
     */
    public int[] multiplyMatrixByVector(String matrixId, int[][] matrix, String vectorId, int[] vector) throws OctaveIOException {
	try {
	    OctaveEngine octave = new OctaveEngineFactory().getScriptEngine();
	    octave.eval(transformMatrixToOctave(matrixId, matrix));
	    octave.eval(transformeVectorToOctave(vectorId, vector));
	    octave.eval("result = " + matrixId + "*" + vectorId);
	    OctaveDouble result = octave.get(OctaveDouble.class, "result");
	    int[] resultV = transformOctaveToJava(matrix.length, result);
	    return resultV;
	} catch (OctaveEvalException e) {
	    throw new IllegalArgumentException(e.getMessage());
	}
    }

    /**
     * Performs the transpose operation for the given matrix.
     * 
     * @param identifier
     *            A identifier of the matrix
     * @param matrix
     *            A matrix to transpose.
     * @return The transpose matrix of the given matrix.
     */
    public int[][] transposeMatrix(String identifier, int[][] matrix) throws OctaveIOException {
	try {
	    OctaveEngine octave = new OctaveEngineFactory().getScriptEngine();
	    octave.eval(transformMatrixToOctave(identifier, matrix));
	    octave.eval("result = transpose(" + identifier + ")");
	    OctaveDouble result = octave.get(OctaveDouble.class, "result");
	    int[][] resultM = transformOctaveToJava(matrix[0].length, matrix.length, result);
	    return resultM;
	} catch (OctaveEvalException e) {
	    throw new IllegalArgumentException(e.getMessage());
	}
    }

    private int[] transformOctaveToJava(int size, OctaveDouble result) {
	int resultM[] = new int[size];
	for (int i = 1; i <= size; i++) {
	    resultM[i - 1] = (int) result.get(i);
	}
	return resultM;
    }

    private int[][] transformOctaveToJava(int rows, int columns, OctaveDouble result) {
	int resultM[][] = new int[rows][columns];
	for (int i = 1; i <= rows; i++) {
	    for (int j = 1; j <= columns; j++) {
		resultM[i - 1][j - 1] = (int) result.get(i, j);
	    }
	}
	return resultM;
    }

    private String transformMatrixToOctave(String identifier, int[][] sourceM) {
	StringBuilder builder = new StringBuilder();
	builder.append(identifier).append("=[");
	for (int i = 0; i < sourceM.length; i++) {
	    for (int j = 0; j < sourceM[0].length; j++) {
		builder.append(sourceM[i][j]);
		if (j < sourceM[0].length - 1) {
		    builder.append(",");
		}
	    }
	    if (i < sourceM.length - 1) {
		builder.append(";");
	    }
	}
	builder.append("]");
	return builder.toString();
    }

    private String transformeVectorToOctave(String identifier, int[] sourceV) {
	StringBuilder builder = new StringBuilder();
	builder.append(identifier).append("=[");
	for (int i = 0; i < sourceV.length; i++) {
	    builder.append(sourceV[i]);
	    if (i < sourceV.length - 1) {
		builder.append(";");
	    }
	}
	builder.append("]");
	return builder.toString();
    }
}
